/**
 * 
 */
package br.org.bertol.mestrado.engine.optimisation.entity;

import java.util.ArrayList;
import java.util.EnumMap;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.fitness.AbstractCombinatorialFitness;
import br.org.bertol.mestrado.engine.optimisation.pso.LinearInertiaReduction;
import br.org.bertol.mestrado.engine.optimisation.pso.Velocity;

/**
 * @author contaqualquer
 */
public class Particle extends AbstractUnit<Integer> {

    /**
     * Velocidade da partícula.
     */
    private transient ArrayList<Velocity>          speed;

    /**
     * Melhor caminho da partícula.
     */
    private transient ArrayList<Integer>           pbest;

    /**
     * Peso do melhor caminho da partícula.
     */
    private transient EnumMap<Objective, Number>   hpbest;

    /**
     * Melhor caminho global.
     */
    private transient ArrayList<Integer>           gbest;

    /**
     * Peso do melhor caminho global.
     */
    private transient EnumMap<Objective, Number>   hgbest;

    /**
     * Taxa de redução da inércia.
     */
    private final transient LinearInertiaReduction linearReduction;

    /**
     * Fator cognitivo.
     */
    private final transient float                  fCognitive;

    /**
     * Fator social.
     */
    private final transient float                  fSocial;

    /**
     * Fator de constrição da velocidade.
     */
    private final transient float                  fConstric;

    /***/
    private transient int                          espacialPosition;

    /**
     * Cria uma nova partícula.
     * @param objectives
     *            Objetivos do problema
     * @param lReduction
     *            Redução linear da inércia
     * @param fcognitive
     *            Fator cognitivo
     * @param fsocial
     *            Fator social
     */
    public Particle(final LinearInertiaReduction lReduction,
        final float fcognitive, final float fsocial,
        final AbstractCombinatorialFitness<Integer> fitness,
        final Objective... objectives) {
        super(fitness, objectives);

        speed = new ArrayList<Velocity>();

        pbest = new ArrayList<Integer>();

        hpbest = new EnumMap<Objective, Number>(Objective.class);

        gbest = new ArrayList<Integer>();

        hgbest = new EnumMap<Objective, Number>(Objective.class);

        // caso os fatores de multiplica��o sejam negativos
        // multiplica todos por -1
        this.linearReduction = lReduction;

        this.fCognitive = fcognitive < 0 ? fcognitive * -1
                : fcognitive;

        this.fSocial = fsocial < 0 ? fsocial * -1 : fsocial;

        final float t = (float) (2 / (2 - (this.fSocial + this.fCognitive) - Math
                .sqrt(Math.pow((this.fSocial + this.fCognitive), 2)
                - (4 * (this.fSocial + this.fCognitive)))));

        this.fConstric = t < 0 ? t * -1 : t;
    }

    /**
     * Atualiza p best.
     */
    @SuppressWarnings("unchecked")
    public final void updatePBest() {
        // atualiza caminho
        pbest = (ArrayList<Integer>) (route).clone();

        // atualiza peso
        hpbest = height.clone();
    }

    /**
     * Atualiza g best.
     */
    @SuppressWarnings("unchecked")
    public final void updateGbest() {
        gbest = (ArrayList<Integer>) (pbest).clone();

        // atualiza peso
        hgbest = hpbest.clone();
    }

    /**
     * @param particle
     *            PArtícula a ter os elementos capturados
     */
    @SuppressWarnings("unchecked")
    public final void updateGbest(final Particle particle) {
        gbest = (ArrayList<Integer>) particle.getRoute().clone();

        hgbest = particle.getHeight().clone();
    }

    /**
     * Calcula a velocidade da part�cula.
     * @param kIteration
     *            Iteração atual
     */
    @SuppressWarnings("unchecked")
    public final void updateVelocity(final int kIteration) {

        final ArrayList<Velocity> gBestSpeed =
                (ArrayList<Velocity>) multiplication(gbest,
                                                     route,
                                                     fSocial)
                .clone();

        final ArrayList<Velocity> pBestSpeed =
                (ArrayList<Velocity>) multiplication(pbest,
                                                     route,
                                                     fCognitive)
                .clone();

        final ArrayList<Velocity> vXInertia =
                (ArrayList<Velocity>) multiplication(
                                                     speed,
                                                     linearReduction
                .get(kIteration))
                .clone();

        gBestSpeed.trimToSize();

        pBestSpeed.addAll(gBestSpeed);

        pBestSpeed.trimToSize();

        vXInertia.addAll(pBestSpeed);

        vXInertia.trimToSize();

        // inverte a ordem
        // Collections.reverse(V_x_inertia);

        // atualiza velocidade
        speed = multiplication(vXInertia, fConstric);

    }

    /**
     * Realiza multiplica��o do array de velocidade com a inércia.
     * @param speed
     *            Array de velocidade
     * @param factor
     *            Fator de multiplicação
     * @return Array de velocidade
     */
    private ArrayList<Velocity> multiplication(final ArrayList<Velocity> speed,
            final float factor) {
        // caso fator de multiplica��o seja 0, retorna
        if (factor == 0) {
            return new ArrayList<Velocity>(0);
        }

        // entre 0 e 1, truca o array de velocidades
        if (factor < 1 && factor > 0) {
            return multiplicationLower(speed, factor);
        }

        return multiplicationUpper(speed, factor);
    }

    /**
     * Realiza a multiplica��o do array dos arrays de PBest/GBest com a posi��o
     * da ppartácula e os fatores sociais/cognitivos.
     * @param left
     *            Array de psoições
     * @param right
     *            Array de psoições
     * @param factor
     *            fator de multiplicação
     * @return Novo array de velocidade
     */
    private ArrayList<Velocity> multiplication(final ArrayList<Integer> left,
            final ArrayList<Integer> right, final float factor) {
        // caso fator de multiplica��o seja 0, retorna
        if (factor == 0) {
            return new ArrayList<Velocity>(0);
        }

        final Float multiplicationFactor = factor * (float) Math.random();

        // entre 0 e 1, truca o array de velocidades
        if (multiplicationFactor < 1 && multiplicationFactor > 0) {
            return multiplicationLower(subtraction(left, right),
                                       multiplicationFactor);
        }

        return multiplicationUpper(subtraction(left, right),
                                   multiplicationFactor);
    }

    /**
     * Multiplica arrays com fatores negativos.
     * @param speedArray
     *            Array de velocidade
     * @param mFactor
     *            Fator de multiplicação
     * @return Noovo array de velocidade
     */
    private ArrayList<Velocity> multiplicationLower(
            final ArrayList<Velocity> speedArray,
            final float mFactor) {
        // trunca o array em quest�o, retornando o array truncado
        final Float truncateArray = mFactor * speedArray.size();

        final ArrayList<Velocity> trucatedSpeed = new ArrayList<Velocity>(
                speedArray.subList(0, truncateArray.intValue()));

        return trucatedSpeed;
    }

    /**
     * Multiplica arrays com fatores positivos.
     * @param speedArray
     *            Array de velocidade
     * @param mFactor
     *            Fator de multiplicação
     * @return Novo array de velocidade
     */
    private ArrayList<Velocity> multiplicationUpper(
            final ArrayList<Velocity> speedArray,
            final Float mFactor) {
        final ArrayList<Velocity> newSpeedArray = new ArrayList<Velocity>(0);

        // parte decimal do array de velocidades
        final Float decimal = mFactor
                - mFactor.intValue();

        // soma o array com as partes inteiras
        for (int i = 0; i < mFactor.intValue(); i++) {
            newSpeedArray.addAll(speedArray);
        }

        // adiciona a parte fracion�ria
        newSpeedArray.addAll(multiplicationLower(newSpeedArray, decimal));

        return newSpeedArray;
    }

    /**
     * Recebe as listas de velocidade e pbest ou gbest, dependendo da situaçãoo
     * e monta novo array de velocidade para cálculo.
     * @param array1
     *            Array de posição 1
     * @param array2
     *            Array de posição 2
     * @return Array com as permutações
     */
    private ArrayList<Velocity> subtraction(final ArrayList<Integer> array1,
            final ArrayList<Integer> array2) {
        final ArrayList<Velocity> newVelocity = new ArrayList<Velocity>(0);

        final int pairSize = array1.size();

        // verifica itera o array da direita at� o seu final
        for (int leftIndex = 1; leftIndex <= pairSize; leftIndex++) {
            // pega item do array da esquerda
            final int leftItem = array1.indexOf(leftIndex);

            final int rightIndex = array2.get(leftItem);

            if (leftIndex != rightIndex) {
                newVelocity.add(new Velocity(leftIndex, rightIndex));
            }
        }

        return newVelocity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final String toString() {
        final StringBuilder buffer = new StringBuilder();

        buffer.append("Posição no espaço: ").append("[")

        .append(espacialPosition).append("] |       ");

        buffer.append(super.toString());

        buffer.append("Resultado").append("[").append(hpbest.toString())
                .append("]");

        return buffer.toString();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public final Object clone() throws CloneNotSupportedException {
        final Particle clone = (Particle) super.clone();

        clone.pbest = (ArrayList<Integer>) this.pbest.clone();

        clone.gbest = (ArrayList<Integer>) this.gbest.clone();

        clone.speed = (ArrayList<Velocity>) this.speed.clone();

        clone.hpbest = this.hpbest.clone();

        clone.hgbest = this.hgbest.clone();

        return clone;
    }

    @Override
    public final void update() {
        updatePBest();
    }

    /**
     * @return the hbest
     */
    public final EnumMap<Objective, Number> getHpbest() {
        return hpbest;
    }

    /**
     * @return the speed
     */
    public final ArrayList<Velocity> getSpeed() {
        return speed;
    }

    /**
     * Posição espacial da partícula noespaço de busca.
     * @return int Posição
     */
    public final int getSpacialPosition() {
        return espacialPosition;
    }

    public final void setSpacialPosition(final int espacialPosition) {
        this.espacialPosition = espacialPosition;
    }
}
